﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using VMultiAPI;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace ZSens
{
    public abstract class Action
    {
        public abstract JObject jSerialize();
        public abstract void DoVMultiUpdate();

        public static Action jDeserializeDispatch(JObject jAction, Widget widget)
        {
            String type = (String)jAction.GetValue("type");
            if (type == "PressJoystickButtonAction")
            {
                return PressJoystickButtonAction.jDeserialize(jAction, widget);
            }
            else if (type == "SetJoystickAxisAction")
            {
                return SetJoystickAxisAction.jDeserialize(jAction, widget);
            }
            else if (type == "PressKeyboardKeyAction")
            {
                return PressKeyboardKeyAction.jDeserialize(jAction, widget);
            }
            else if (type == "FloatToBool")
            {
                return FloatToBool.jDeserialize(jAction, widget);
            }
            else if (type == "BoolToFloat")
            {
                return BoolToFloat.jDeserialize(jAction, widget);
            }
            else return null;
        }
        public virtual string GetDisplayString()
        {
            return "nyi";
        }
        public abstract Action PartialCopy(Widget widget);
    }
    public abstract class FloatAction
        : Action
    {
        public FloatAction(FloatProvider widget)
        {
            this.widget = widget;
        }
        protected FloatProvider widget;
    }
    public abstract class BoolAction
        : Action
    {
        public BoolAction(BoolProvider widget)
        {
            this.widget = widget;
        }
        protected BoolProvider widget;
    }
    public enum JoystickAxis
    {
        X,Y,
        RotateX,RotateY,
        Throttle,Hat
    }
    public class SetJoystickAxisAction
        : FloatAction
    {
        public override Action PartialCopy(Widget _widget)
        {
            if (widget.IsWidget())
            {
                return new SetJoystickAxisAction((FloatProvider) _widget, this.axis);
            }
            else
            {
                return new SetJoystickAxisAction((FloatProvider)(
                    ((Action) widget).PartialCopy(_widget)), this.axis);
            }
        }

        public static SetJoystickAxisAction jDeserialize(JObject jAction, Widget provider)
        {
            JoystickAxis axis = (JoystickAxis) ((int) jAction.GetValue("axis"));
            if (jAction.GetValue("overlay") == null)
            {
                return new SetJoystickAxisAction((FloatProvider)provider, axis);
            }
            else
            {
                JObject _jAction = (JObject) jAction.GetValue("overlay");
                return new SetJoystickAxisAction(
                    (FloatProvider) Action.jDeserializeDispatch(_jAction, provider), axis);
            }
        }

        public override JObject jSerialize()
        {
            JObject setJoystickAxisActionObject = new JObject();
            setJoystickAxisActionObject.Add("type", "SetJoystickAxisAction");
            setJoystickAxisActionObject.Add("axis", (int) this.axis);
            if (widget.IsWidget() == false)
            {
                setJoystickAxisActionObject.Add("overlay", ((Action)widget).jSerialize());
            }
            return setJoystickAxisActionObject;
        }

        public SetJoystickAxisAction(FloatProvider widget,JoystickAxis axis)
            : base(widget)
        {
            this.axis = axis;
        }
        private JoystickAxis axis;
        public override string GetDisplayString()
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("Joystick Axis");
            sb.AppendLine(axis.ToString());
            return sb.ToString();
        }
        public override void DoVMultiUpdate()
        {
            float value = widget.GetState();
            if (value > 1) value = 1;
            if (value < 0) value = 0;
            switch (axis)
            {
                case JoystickAxis.X:
                    VMulti.GetInstance().Joystick.X = (sbyte)((value - 0.5) * 127);
                    break;
                case JoystickAxis.Y:
                    VMulti.GetInstance().Joystick.Y = (sbyte)((value - 0.5) * 127);
                    break;
                case JoystickAxis.RotateX:
                    VMulti.GetInstance().Joystick.RotateX = (sbyte)((value - 0.5) * 127);
                    break;
                case JoystickAxis.RotateY:
                    VMulti.GetInstance().Joystick.RotateY = (sbyte)((value - 0.5) * 127);
                    break;
                case JoystickAxis.Throttle:
                    VMulti.GetInstance().Joystick.Throttle = (sbyte)((value - 0.5) * 127);
                    break;
                case JoystickAxis.Hat:
                    VMulti.GetInstance().Joystick.HatPosition = (byte)(value * 255);
                    break;
            }
        }
    }
    public class PressJoystickButtonAction
            : BoolAction
    {
        public static PressJoystickButtonAction jDeserialize(JObject jAction, Widget widget)
        {
            List<int> _buttons = new List<int>();
            JArray jButtons = (JArray) jAction.GetValue("buttons");
            for (int i = 0; i < jButtons.Count; i++)
            {
                _buttons.Add((int) jButtons[i]);
            }
            JToken overlayAction = jAction.GetValue("overlay");

            if (overlayAction == null)
            {
                return new PressJoystickButtonAction((BoolProvider) widget, _buttons);
            }
            else
            {
                return new PressJoystickButtonAction(
                    (BoolProvider) Action.jDeserializeDispatch((JObject) overlayAction, widget), _buttons);
            }
        }

        public override JObject jSerialize()
        {
            JObject setJoystickButtonActionObject = new JObject();
            setJoystickButtonActionObject.Add("type", "PressJoystickButtonAction");
            JArray joystickButtons = new JArray();
            foreach (int btn in buttons)
            {
                joystickButtons.Add(btn);
            }
            setJoystickButtonActionObject.Add("buttons", joystickButtons);
            if (widget.IsWidget() == false)
            {
                setJoystickButtonActionObject.Add("overlay", ((Action)widget).jSerialize());
            }
            return setJoystickButtonActionObject;
        }

        public override Action PartialCopy(Widget _widget)
        {
            List<int> btnsClone = new List<int>();
            foreach (int btn in buttons)
            {
                btnsClone.Add(btn);
            }

            if (widget.IsWidget())
            {
                return new PressJoystickButtonAction((BoolProvider)_widget, btnsClone);
            }
            else
            {
                return new PressJoystickButtonAction(
                    (BoolProvider)(((Action)widget).PartialCopy(_widget)), btnsClone);
            }
        }
        public PressJoystickButtonAction(BoolProvider widget, List<int> buttons)
            : base(widget)
        {
            this.buttons = buttons;
        }
        public override string GetDisplayString()
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("JoyStick Button");
            foreach (int btn in buttons)
            {
                sb.AppendLine(btn.ToString());
            }
            return sb.ToString();
        }
        public override void DoVMultiUpdate()
        {
            var pressed = widget.GetState();
            foreach (var i in buttons)
            {
                if (i >= 0 && i < 16)
                    VMulti.GetInstance().Joystick.Buttons[i].Pressed = pressed;
            }
        }
        private List<int> buttons;
    }
    public class PressKeyboardKeyAction
        : BoolAction
    {
        public static PressKeyboardKeyAction jDeserialize(JObject jAction, Widget widget)
        {
            List<Keyboard.KeyCode> _keys = new List<Keyboard.KeyCode>();
            JArray jKeys = (JArray)jAction.GetValue("keys");
            for (int i = 0; i < jKeys.Count; i++)
            {
                _keys.Add((Keyboard.KeyCode) ((int) jKeys[i]));
            }
            JToken overlayAction = jAction.GetValue("overlay");

            if (overlayAction == null)
            {
                return new PressKeyboardKeyAction((BoolProvider)widget, _keys);
            }
            else
            {
                return new PressKeyboardKeyAction(
                    (BoolProvider)Action.jDeserializeDispatch((JObject)overlayAction, widget), _keys);
            }
        }

        public override JObject jSerialize()
        {
            JObject pressKeyboardKeyActionObject = new JObject();
            pressKeyboardKeyActionObject.Add("type", "PressKeyboardKeyAction");
            JArray keyboardKeys = new JArray();
            foreach (Keyboard.KeyCode key in keys)
            {
                keyboardKeys.Add((int) key);
            }
            pressKeyboardKeyActionObject.Add("keys", keyboardKeys);
            if (widget.IsWidget() == false)
            {
                pressKeyboardKeyActionObject.Add("overlay", ((Action)widget).jSerialize());
            }
            return pressKeyboardKeyActionObject;
        }
        public override Action PartialCopy(Widget _widget)
        {
            List<Keyboard.KeyCode> keysClone = new List<Keyboard.KeyCode>();
            foreach (Keyboard.KeyCode key in keys)
            {
                keysClone.Add(key);
            }

            if (widget.IsWidget())
            {
                return new PressKeyboardKeyAction((BoolProvider)_widget, keysClone);
            }
            else
            {
                return new PressKeyboardKeyAction((BoolProvider)(
                    ((Action) widget).PartialCopy(_widget)), keysClone);
            }
        }
        public PressKeyboardKeyAction(BoolProvider widget, List<Keyboard.KeyCode> keys)
            :base (widget)
        {
            this.keys = keys;
        }
        public override string GetDisplayString()
        {
            StringBuilder sb = new StringBuilder();
         //   sb.AppendLine("Keyboard");
            foreach (Keyboard.KeyCode key in keys)
            {
                sb.AppendLine(VMultiAPI.Keyboard.KeyToString[(int)key]);
            }
            return sb.ToString();
        }

        public override void DoVMultiUpdate()
        {
            var pressed = widget.GetState();
            foreach (var i in keys)
            {
                VMulti.GetInstance().Keyboard.GetKey(i).Pressed = pressed;
            }
        }
        private List<Keyboard.KeyCode> keys;
    }
    public class Range
    {
        public float Start { get; set; }
        public float End { get; set; }
        public Range()
        {
        }

        public float Normalize(float value)
        {
            var retVal = (value - Start) / (End - Start);
            return retVal > 1 ? 1: retVal < 0 ? 0 : retVal;
        }
    }
    public class FloatToBool
        : FloatAction, BoolProvider
    {
        public bool IsWidget()
        {
            return false;
        }

        public static FloatToBool jDeserialize(JObject jAction, Widget _widget)
        {
            float _sensitivity = (float)jAction.GetValue("sensitivity");
            Range _range = new Range { Start = (float) jAction.GetValue("start"), End = (float) jAction.GetValue("end") };
            JToken overlayAction = jAction.GetValue("overlay");
            if (overlayAction == null)
            {
                return new FloatToBool((FloatProvider)_widget, _sensitivity, _range);
            }
            else
            {
                return new FloatToBool((FloatProvider)Action.jDeserializeDispatch((JObject)overlayAction, _widget), _sensitivity, _range);
            }
        }

        public override JObject jSerialize()
        {
            JObject floatToBoolActionObject = new JObject();
            floatToBoolActionObject.Add("type", "FloatToBool");
            floatToBoolActionObject.Add("sensitivity", this.sensitivity);
            floatToBoolActionObject.Add("start", this.effectiveRange.Start);
            floatToBoolActionObject.Add("end", this.effectiveRange.End);
            if (widget.IsWidget() == false)
            {
                floatToBoolActionObject.Add("overlay", ((Action)widget).jSerialize());
            }
            return floatToBoolActionObject;
        }

        public override Action PartialCopy(Widget _widget)
        {
            if (widget.IsWidget())
            {
                return new FloatToBool((FloatProvider)_widget, 
                    this.sensitivity,
                    new Range { Start = effectiveRange.Start, End = effectiveRange.End });
            }
            else
            {
                return new FloatToBool((FloatProvider)(((Action) widget).PartialCopy(_widget)), 
                    this.sensitivity, 
                    new Range { Start = effectiveRange.Start, End = effectiveRange.End });
            }
        }

        public FloatToBool(FloatProvider widget, float sensitivity, Range effectiveRange)
            : base(widget)
        {
            this.sensitivity = sensitivity;
            this.effectiveRange = effectiveRange;
            currentState = false;
            pressTimes = 0;
        }
        public override void DoVMultiUpdate()
        {
            // Note that the adapter do not actually UPDATE anything
        }
        public bool GetState()
        {
            floatValue = effectiveRange.Normalize(widget.GetState());
            if (floatValue < 0) floatValue = 0;
            
            if (floatValue > sensitivity)
                pressProportion = 1;
            else
                pressProportion = floatValue / sensitivity; //TODO: May adjust this to achieve better usability
            if (!currentState && pressTimes >= 30 * (1 - pressProportion))
            {
                currentState = true;
                pressTimes = 0;
            }
            if (currentState && pressTimes >= 30 * pressProportion)
            {
                currentState = false;
                pressTimes = 0;
            }
            if (pressProportion == 1)
                currentState = true;
            if (pressProportion == 0)
                currentState = false;
            pressTimes++;
            return currentState;
        }
        private bool currentState;
        private float sensitivity;
        private Range effectiveRange;
        private float floatValue;
        private float pressProportion;
        private int pressTimes;
    }
    public class BoolToFloat
        : BoolAction, FloatProvider
    {
        public bool IsWidget()
        {
            return false;
        }

        public static BoolToFloat jDeserialize(JObject jAction, Widget _widget)
        {
            float _sensitivity = (float)jAction.GetValue("sensitivity");
        
            JToken overlayAction = jAction.GetValue("overlay");
            if (overlayAction == null)
            {
                return new BoolToFloat((BoolProvider)_widget, 
                    new BoolToFloatValueStore(), 
                    _sensitivity, 
                    (Type) ((int) jAction.GetValue("BoolToFloatType")));
            }
            else
            {
                return new BoolToFloat((BoolProvider)Action.jDeserializeDispatch((JObject)overlayAction, _widget), 
                    new BoolToFloatValueStore(), 
                    _sensitivity, 
                    (Type)((int)jAction.GetValue("BoolToFloatType")));
            }
        }

        public override JObject jSerialize()
        {
            JObject boolToFloatActionObject = new JObject();
            boolToFloatActionObject.Add("type", "BoolToFloat");
            boolToFloatActionObject.Add("sensitivity", this.sensitivity);
            boolToFloatActionObject.Add("BoolToFloatType", (int) type);
            if (widget.IsWidget() == false)
            {
                boolToFloatActionObject.Add("overlay", ((Action)widget).jSerialize());
            }
            return boolToFloatActionObject;
        }

        public override Action PartialCopy(Widget _widget)
        {
            if (widget.IsWidget())
            {
                return new BoolToFloat((BoolProvider)_widget, 
                    new BoolToFloatValueStore(),
                   this.sensitivity, this.type);
            }
            else
            {
                return new BoolToFloat(
                    (BoolProvider)((((Action)widget).PartialCopy(_widget)).PartialCopy(_widget)),
                    new BoolToFloatValueStore(),
                   this.sensitivity, this.type);
            }
        }

        public class BoolToFloatValueStore
        {
            public float value;
        }
        public enum Type
        {
            Increase,
            Decrease
        }
        public BoolToFloat(BoolProvider widget, BoolToFloatValueStore valueStore, float sensitivity, Type type)
            :base (widget)
        {
            this.sensitivity = sensitivity;
            this.valueStore = valueStore;
            this.type = type;
        }
        public override void DoVMultiUpdate()
        {
            
        }
        public float GetState()
        {
            if (widget.GetState())
            {
                if (type == Type.Increase)
                    valueStore.value += sensitivity;
                if (type == Type.Decrease)
                    valueStore.value -= sensitivity;
                if (valueStore.value > 1)
                    valueStore.value = 1;
                if (valueStore.value < 0)
                    valueStore.value = 0;
            }
            return valueStore.value;
        }
        private BoolToFloatValueStore valueStore;
        private float sensitivity;
        private Type type;
    }
}
